package comp9417;

import java.io.FileNotFoundException;
import java.util.HashMap;

public class Evaluator {

    private final Dataset dataset;
    private String classifierAttribute;
    private int attributeIndex;
    private Attribute attribute;
    private HashMap<Integer, Double> stats;

    public Evaluator(String fileName, String classifierAttribute) throws FileNotFoundException {
        dataset = new DataLoader().loadClassificationData(fileName);
        this.classifierAttribute = classifierAttribute;
        attributeIndex = dataset.getAttributeIndex(classifierAttribute);
        attribute = dataset.getAttribute(classifierAttribute);
    }

    public Evaluator(Dataset dataset, String classifierAttribute) throws FileNotFoundException {
        this.dataset = dataset;
        this.classifierAttribute = classifierAttribute;
        attributeIndex = dataset.getAttributeIndex(classifierAttribute);
        attribute = dataset.getAttribute(classifierAttribute);
    }

    public int bestK(int start, int end, int kFold, boolean weighted) {
        stats = new HashMap<Integer, Double>();
        double smallestError = Double.MAX_VALUE;
        int best = 0;
        for (int k = start; k <= end; k++) {
            double error = crossValidation(kFold, k, weighted);
            stats.put(k, error);
            if (error < smallestError) {
                smallestError = error;
                best = k;
            }
        }
        return best;
    }

    public HashMap<Integer, Double> getStats() {
        return stats;
    }

    public double crossValidation(int kFold, int neighborNumber, boolean weighted) {
        double answer = 0;
        for (Fold fold : dataset.createFolds(kFold)) {
            answer += simpleEvaluate(fold, neighborNumber, weighted);
        }
        return answer / kFold;
    }

    /**
     * given training set and test set , will make model using training set and compare it with the test set
     */
    private double simpleEvaluate(Fold fold, int neighborNumber, boolean weighted) {
        int testSize = fold.getTest().getDatapoints().size();
        Classifier classifier = new Classifier(fold.getTraining(), classifierAttribute);
        AttributeType type = attribute.type;
        double answer;

        if (AttributeType.CONTINUOUS.equals(attribute.type)) {
            double[] predicted = new double[testSize];
            double[] expected = new double[testSize];
            int counter = 0;
            for (DataPoint dp : fold.getTest().getDatapoints()) {
                predicted[counter] = (Double) (weighted ? type.predictWeighted(classifier, dp, neighborNumber) : type.predict(classifier, dp, neighborNumber));
                expected[counter] = (Double) dp.getValues()[attributeIndex];
                counter++;
    		}
            answer = this.rootMeanSquareDistance(expected, predicted) / (attribute.max - attribute.min);
        } else {
            String[] predicted = new String[testSize];
            String[] expected = new String[testSize];
            int counter = 0;
            for (DataPoint dp : fold.getTest().getDatapoints()) {
                predicted[counter] = (String) (weighted ? type.predictWeighted(classifier, dp, neighborNumber) : type.predict(classifier, dp, neighborNumber));
                expected[counter] = (String) dp.getValues()[attributeIndex];
                counter++;
            }
            answer = this.classifierError(expected, predicted);
        }
    	return answer;
    }

    private double classifierError(String[] actual, String[] predicted) {
        double error = 0.0;
        for (int index = 0; index < actual.length; index++) {
            if (!actual[index].equals(predicted[index])) error++;
        }
        return error / actual.length;
    }

    private double rootMeanSquareDistance(double[] actual, double[] predicted) {
        int count = 0;
        double runningTotal = 0.0;
        for (int i = 0; i < actual.length; i++) {
            if (Double.isNaN(actual[i])) continue;
            runningTotal += Math.pow((actual[i] - predicted[i]), 2.0);
            count++;
        }
        return Math.sqrt(runningTotal / count);
    }

}
